
/*
 *  Copyright 2007 Marc Lanctot and Marc Gendron-Bellemare
 *
 *  This file is part of OpenMTG.
 *
 *  OpenMTG is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  OpenMTG is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with OpenMTG.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

import java.util.*;

public class GameState {

  private ArrayList<Player> players;
  private int whoDecides;
  private int whoStarts;
  private AttackPhase atkPhase; 
	private PhaseInfo aPhaseInfo;
	private CardEffectStack stack;

  public GameState( ){
    players = new ArrayList<Player>(); 
    whoDecides = -1;
    whoStarts = -1;
    atkPhase = null;
    stack = new CardEffectStack();
		aPhaseInfo = new PhaseInfo();
  }

  public void addPlayer(Player p) {
    // check if no others with same id
    players.add(p);
  }

  public int getNumPlayers() { 
    return players.size();
  }

  public Player getPlayer(int id) {
    if (id >= players.size())
      return null;
    return players.get(id);
  }
  
  public void setDeck(int playerId, Deck deck)
  {
    Player player = getPlayer(playerId); 
    player.setDeck(deck); 
  }

  public boolean ready()
  {
    for (Player p : players)
      if (!p.ready())
        return false;

    return true;
  }

  public int whoDecides() {
    if (whoDecides < 0)
      whoDecides = Helpers.RNG.nextInt(getNumPlayers()); 
    return whoDecides;
  }

  public int setWhoStarts(int id)
  {
    if (id < 0) {
      for (Player p : players)
      {
        if (p.getId() != whoDecides()) {
          whoStarts = p.getId(); 
          break;
        }
      }
    }
    else {
      whoStarts = id;
    }

    return whoStarts;
  }
  
  public boolean containsPlayerWithName(String name) {    
    for (Player p : players)
      if (p.getName().equals(name))
        return true;
    
    return false;
  }

  public void setNotAttacking() {
    for (Player p : players) 
      for (Card c : p.getCreatures())
        c.setAttacking(false); 
  }
  
  public boolean mulligan(int id) 
  {
    Player p = players.get(id);
    if (p.getHandSize() == 0)
      return false;

    int handSize = p.getHandSize(); 

    int cardLoss = 1;

    if (p.hasAllLand() || p.hasNoLand())  
      cardLoss = 0; 

    p.getDeck().putOnBottom(p.getHand());
    p.clearHand();  
    Dealer.linearShuffle(p.getDeck()); 

    int newHandSize = handSize - cardLoss;

    p.draw(newHandSize);

    return true;
  }

  public Card playLand(int pid, int iid)
  {
    Player p = players.get(pid); 
    Card c = p.getCardInHand(iid);

    if (c == null)
      return null;

    if (p.getId() != c.getOID())  // not owner of land?
      return null; 

    if (!c.isLand())
      return null; // not playing a land?

    if (!p.playLand(c))
      return null;

		c.setSickness(true);

    return c;
  }

  public Card getCard(int iid)
  {
    for (Player p : players) {

      for (Card c : p.getHand()) 
        if (c.getIID() == iid) return c;

      for (Card c : p.lands) 
        if (c.getIID() == iid) return c;

      for (Card c : p.creatures) 
        if (c.getIID() == iid) return c;
      
      for (Card c : p.artifacts) 
        if (c.getIID() == iid) return c;
      
      for (Card c : p.enchantments) 
        if (c.getIID() == iid) return c;
    }

    return null;
  }

  public boolean tapForMana(int pid, int iid)
  {
    Player p = players.get(pid);
    Card c = p.getLand(iid);

		// Also handle summoning sickness? special case of creature-land...
    if (c == null || !c.isTappable())
      return false;

    if (c.getName().equals("Plains"))
      p.addMana('W');
    else if (c.getName().equals("Forest"))
      p.addMana('F');
    else if (c.getName().equals("Island"))
      p.addMana('U');
    else if (c.getName().equals("Mountain"))
      p.addMana('M');
    else if (c.getName().equals("Swamp"))
      p.addMana('B');
    else 
      return false;

    c.tap(); 
   
    return true;
  }

  // only working with creatures right now
  //
  public Card cast(int pid, int iid, int turnPlayerId) {
    Player p = players.get(pid); 

		// Make sure we have the said card
    Card c = p.getCardInHand(iid);
		if (c == null)
			return null;
		else if (c.isOnTurnOnlyCast() && (pid != turnPlayerId))
		  return null;
		else if (getStackSize() > 0 && !c.canCastInResponse())
		  return null;
		else if (c.isLand())
			return null;
		else if (!c.validCastPhase(aPhaseInfo.getPhase()))
			return null;
    else if (!p.hasManaFor(iid))
      return null; 

    // remove the mana for it
    p.removeManaFor(iid);

    if (!p.removeCardFromHand(c)) 
      return null; 

    // put the card on the current spell stack
    CardEffect effect = c.getMainEffect(); 
    effect.setInvokerId(pid);
    stack.push(effect);
    
    return c;
  }
    
  public void resolveStack(ArrayList<Message> responses)
  {
    // these are the list of responses sent back to each client 
    while (!stack.empty())
    {
      CardEffect effect = stack.pop();      
      resolveEffect(effect, responses);
    }
    
  }
 
 	public boolean stackIsEmpty()
	{
		return stack.isEmpty();
	}
	
  public boolean attack(int a_pid, int d_pid, int iid) {

    Player ap = players.get(a_pid);
    Player dp = players.get(d_pid); 

    Card ac = ap.getCardInCreatures(iid);
    // check for card existence, ownership, whether it can attack (includes
		//  a bunch of things)
		if (ac != null) System.err.println ("Card can attack: "+ac.canAttack());

		if (ac == null) return false;
		else if (ac.getOID() != a_pid) return false;
		else if (!ac.canAttack()) return false;
		
    if (atkPhase == null)
    {
      atkPhase = new AttackPhase(ap, dp); 
      System.err.println ("Created attack phase");
    }

    atkPhase.addAttacker(ac);
    
    ac.tap();
    ac.setAttacking(true); 
    
    return true;
  }

	public boolean canBlock(int pPID)
	{
		// Make sure we're the guy who should be blocking
		if (atkPhase == null) return false;
		else if (atkPhase.getDefendingPlayer().getId() != pPID) return false;
		return true;
	}
	
  public boolean block(int pPID, int pBlocker, int pAttacker)
	{
    if (getPhase() != PhaseInfo.Phase.BLOCK)
    {
      //send("ERROR Not the block phase!");
      return false;
    }
    
		Player dp = getPlayer(pPID); 
    Player ap = atkPhase.getAttackingPlayer();
		
		Card blocker = dp.getCreature(pBlocker); 
		Card attacker = ap.getCreature(pAttacker);
		
		if (blocker == null || attacker == null)
		{
		  //send("ERROR Creature doesn't exist?");
		  return false; 
		}
	
    if (atkPhase.isBlocking(blocker.getIID()))
      return false;

		atkPhase.addBlocker(blocker, attacker); 
		
    return true; 
  }
  
  public boolean done_blocking(int d_pid)
  {
    return true;
  }

	public ArrayList<CombatGroup> resolveAttack(ArrayList<Message> pResponses)
	{
		if (atkPhase == null)
			return null;
    
		ArrayList<CombatGroup> attackers = atkPhase.getAttackers();
		HashMap<CombatGroup,CombatGroup> blockers =
			atkPhase.getBlockers();
		ArrayList<CombatGroup> manualDamage = new ArrayList<CombatGroup>();

		Player def = atkPhase.getDefendingPlayer();
		Player off = atkPhase.getAttackingPlayer();

		for (CombatGroup attacker : attackers) {

      // check if it's being blocked
      
      CombatGroup blockedBy = null;
			
      for (CombatGroup blocker : blockers.keySet()) {
        CombatGroup attacker2 = blockers.get(blocker); 
        
				// @@@ assumption that blockers' values are taken from attackers
				//  (see AttackPhase.addBlocker)
        if (attacker2 == attacker)
				{
          blockedBy = blocker;
					break;
				}
      }
     
      // if not, hits player
      
      if (blockedBy == null) {
				int dam = attacker.getPower();

        def.addLifePoints(-dam);
				// Send a DAMAGE message, with -1 as the target
				pResponses.add (new Message (-1,
					"DAMAGE "+def.getId()+" -1 "+dam));
      }
     	else
			{
				blockedBy.addDamage (attacker.getPower());
				attacker.addDamage (blockedBy.getPower());

				// 1. Resolve automatic damage
				boolean autoAttackersDamage = true;
				boolean autoBlockersDamage = true;

				if (blockedBy.aMembers.size() > 1)
					// @@@ Check for banding
					autoAttackersDamage = false;
				if (attacker.aMembers.size() > 1)
					autoBlockersDamage = false;

				if (!autoAttackersDamage)
        {
					manualDamage.add(blockedBy);
          blockedBy.setDamageOwner(off.getId());
        }
				else
				{
					int dam = blockedBy.getDamage();
					Card c = blockedBy.assignDamage();
					if (c != null)
						pResponses.add (new Message(-1,
							"DAMAGE "+def.getId()+" "+c.getIID()+" "+dam));
				}
				if (!autoBlockersDamage)
        {
					manualDamage.add(attacker);
          attacker.setDamageOwner(def.getId());
        }
				else
				{
					int dam = attacker.getDamage();
					Card c = attacker.assignDamage();
					if (c != null)
						pResponses.add (new Message(-1,
							"DAMAGE "+off.getId()+" "+c.getIID()+" "+dam));
				}
			}
    }

    atkPhase.setManualDamage(manualDamage);

		return manualDamage;
	}

  public boolean assignDamage(int pid, int victim, int from, int damage)
  {
    if (damage < 0) return false;

    // Find which combat group the victim belongs to
    CombatGroup group = atkPhase.getManualDamageGroup(victim);
    System.out.println ("Assigning damage, "+pid+" "+victim+" "+from+" "+
      damage+" group is "+group);

    if (group == null) return false;

    System.out.println ("Check damage owner");
    // Determine whether the assigner is the person who should be assigning
    if (group.getDamageOwner() != pid) return false;

    // No cheating!
    System.out.println ("Test cheating");
    if (group.getDamage() < damage) return false;

    System.out.println ("Hit it from "+group.getDamage()+" with "+damage);
    // @@@ handle the 'from' field
    group.assignDamage(group.getCard(victim), damage);

    if (group.getDamage() <= 0)
    {
      atkPhase.removeManualDamageGroup(group);
      System.out.println ("Removed group from manual damage");
    }

    return true;
  }

  public boolean doneDamageAssignment()
  {
    if (atkPhase == null) return false;
    if (atkPhase.getManualDamage() == null) return false;
    return atkPhase.getManualDamage().isEmpty();
    
  }
  
	public void cleanupAttack()
	{
    // Unsets the attack flags of all the creatures that attacked / blocked
	  atkPhase.setNotAttacking();
	  
		atkPhase = null;
	}

	/** This method removes dead creatures and puts them in the graveyard.
	  * This should be called after damage prevention has been done.
		* It should also potentially remove dienchanted enchantments, shattered
		* artifacts and the like.
		*/
	public void cleanupDead(ArrayList<Message> pResponses)
	{
		for (Player p : players)
		{
			ArrayList<Card> creatures = p.getCreatures();
			LinkedList<Card> collected = new LinkedList<Card>();
			
			for (Card c : creatures)
				if (c.damage >= c.toughness)
					collected.add (c);

			for (Card c : collected)
			{
				p.removeFromCreatures(c, pResponses);
				p.addToGraveyard(c,pResponses);
			}
		}
	}
	
  public String getAtkPhaseResults() 
  {
     return atkPhase.getResults();
  }
 
 	/** Returns whether the current player is attacking (and therefore we 
	  * should not go into postmain phase).
		*/
 	public boolean isAttacking()
	{
		return (atkPhase != null);
	}

	public Player getDefendingPlayer()
	{
		if (atkPhase == null) return null;
		else return atkPhase.getDefendingPlayer();
	}
	
  // Keep this method at the bottom
  // Maybe we'll turn it into a class later.
  
  private void resolveEffect(CardEffect effect, ArrayList<Message> responses)
  {
    int type = effect.getType(); 
    Card card = effect.getCardRef();
    Player p = players.get(effect.getInvokerId());
    
    switch(type) 
    {
      case Constants.EFTYPE_SUMMON:
      {
        // as i said, only works for creatures
        p.creatures.add(card);

        // Some cards come without summoning sickness...
        if (card.getPermanent())
          card.setSickness(true);

        responses.add(new Message(-1, "RESEFF " + type + " " + p.getId() + " " + card.encode()));
      }
      break;
    }
  }

	public PhaseInfo.Phase nextPhase()
	{
		return aPhaseInfo.nextPhase();
	}

	public PhaseInfo.Phase getPhase()
	{
		return aPhaseInfo.getPhase();
	}

	public boolean getNewPhase()
	{
		return aPhaseInfo.getNewPhase();
	}
	
	public void newTurn()
	{
		aPhaseInfo.newTurn();
	}

	public boolean getPlayedLand()
	{
		// Shouldn't this flag directly be in GameState?
		return aPhaseInfo.getPlayedLand();
	}

	public void setPlayedLand(boolean pPlayed)
	{
		aPhaseInfo.setPlayedLand(pPlayed);
	}

	public boolean getAttacked()
	{
		return aPhaseInfo.getAttacked();
	}

	public void setAttacked(boolean pAtck)
	{
		aPhaseInfo.setAttacked(pAtck);
	}

	public PhaseInfo getPhaseInfo() { return aPhaseInfo; }

	public boolean canAttack(int pPID)
	{
		Player player = getPlayer(pPID);
		
		// First check the phase
		if (!aPhaseInfo.getPhase().equals(PhaseInfo.Phase.COMBAT))
			return false;
		
		// Go through all creatures, find out if any of them can attack
		for (Card critter : player.getCreatures())
		{
			if (critter.canAttack()) return true;
		}

		return false;
	}
	
	public int getStackSize() 
	{
	  return stack.size();
	}
}

